/*******************************************************************************
 * Copyright (c) 2014 Francesco Gabbrielli
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Francesco Gabbrielli - initial API and implementation
 ******************************************************************************/
package it.francescogabbrielli.android.solidalapp;

import java.util.TreeSet;

import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient;
import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest;
import com.google.api.client.json.GenericJson;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;

/**
 * Abstract async loader for endpoints data
 *
 * @param <D> the loader type
 */
public abstract class AbstractEndpointsLoader<D> extends AsyncTaskLoader<D> {
		
	private D data;
	
	protected Class<?> objClass;
	protected AbstractGoogleJsonClient endpoint;
	protected Object[] filters;
	protected Class<?>[] filterClasses;
	protected String method;
	
	private Exception error;
		
	/**
	 * Construct a new loader which automatically builds a client endpoint
	 * 
	 * @param context
	 * 		the current context
	 * @param objClass
	 * 		the class of the data object to download, that identifies the endpoint
	 * @param filters
	 * 		filters passed as params to the endpoint call
	 */
	public AbstractEndpointsLoader(Context context, Class<?> objClass, Object... filters) {
		super(context);
		setFilters(filters);
		this.objClass = objClass;
		try {
			endpoint = getEndpoint();
		} catch(Exception e) {
			throw new IllegalArgumentException("Cannot instantiate endpoint for: "+objClass);
		}
	}

	/**
	 * Set filters from bundle data, used for initialization in onCreateLoader
	 * 
	 * @param filtersBundle
	 * 		the filters passed in bundle
	 * @return
	 * 		this loader object
	 */
	public AbstractEndpointsLoader<D> setFilters(Bundle filtersBundle) {
		if(filtersBundle!=null) {
			Object[] filters = new Object[filtersBundle.size()];
			int i=0;
			for(String key : new TreeSet<String>(filtersBundle.keySet()))
				filters[i++] = filtersBundle.get(key);
			setFilters(filters);
		}
		return this;
	}
	
	/**
	 * Set filters 
	 * 
	 * @param filters
	 * 		filters passed as params to the endpoint call
	 */
	protected void setFilters(Object... filters) {
		this.filters = filters;
		filterClasses = new Class[filters.length];
		try {
			for(int i=0; i<filterClasses.length; i++)
				filterClasses[i] = filters[i].getClass();
		} catch(Exception e) {
			filters=null;
			Log.w(SolidalApp.TAG, "Error setting filters for: "+this, e);
		}
	}
	
	/**
	 * Get the endpoint remote method that will be called
	 * 
	 * @return
	 * 		the name of the method
	 */
	public String getMethod() {
		return method;
	}
	
	/**
	 * Get the last error verified
	 * 
	 * @return
	 * 		the error
	 */
	public Exception getError() {
		return error;
	}
	
	/**
	 * Set the remote method to call
	 * 
	 * @param method
	 * 		the method name
	 * @return
	 * 		this loader object
	 */
	public AbstractEndpointsLoader<D> setMethod(String method) {
		this.method = method;
		return this;
	}
	
	/**
	 * Build the client endpoint
	 * 
	 * @return
	 * 		the client endpoint
	 * @throws Exception
	 * 		if something bad happens in reflection calls
	 */
	protected AbstractGoogleJsonClient getEndpoint() throws Exception {
		String name = objClass.getName();
		String pkg = name.substring(0, name.indexOf(".model"));
		name = objClass.getSimpleName() + "endpoint";
		@SuppressWarnings("unchecked")
		Class<? extends AbstractGoogleJsonClient> c = 
				(Class<? extends AbstractGoogleJsonClient>) Class.forName(pkg+"."+name);
		return EndpointsUtils.buildEndpoint(c);
	}
	
	/**
	 * Load endpoint data
	 * 
	 * @return
	 * 		the result of the call as JSON data
	 */
	protected synchronized GenericJson load() throws Exception {
		try {
			error = null;
			AbstractGoogleJsonClientRequest<?> req = (AbstractGoogleJsonClientRequest<?>) endpoint.getClass()
					.getMethod(method, filterClasses)
					.invoke(endpoint, filters);
			GenericJson ret = (GenericJson) req.execute();
			if (ret!=null && ret.containsKey("error"))
				throw new Exception(String.valueOf(ret.get("error")));
			return ret;
		} catch(Exception e) {
			Log.e(SolidalApp.TAG, "Error loading "+endpoint, e);
			error = e;
			throw e;
		}
	}
	
	@Override
	public void deliverResult(D data) {
		this.data = data;
		if(isStarted())
			super.deliverResult(data);
	}
	
    /**
     * Handles a request to start the Loader.
     */
    @Override 
    protected void onStartLoading() {
        if (data!= null) {
            // If we currently have a result available, deliver it
            // immediately.
            deliverResult(data);
        }

        if (takeContentChanged() || data == null) {
            // If the data has changed since the last time it was loaded
            // or is not currently available, start a load.
            forceLoad();
        }
    }
    
    /**
     * Handles a request to stop the Loader.
     */
    @Override 
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }
    
    /**
     * Handles a request to completely reset the Loader.
     */
    @Override 
    protected void onReset() {
    	
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        // At this point we can release the resources associated with 'apps'
        // if needed.
        if (data != null) {
//            onReleaseResources(mApps);
            data = null;
        }

    }
}
